﻿using System;
using System.Collections.Generic;

namespace wuo.convert
{
    using SanlieHash.Fast;
    using wuo.helpers;

    internal class Convert_C001 : IWuoConvert
    {
        public object DeserializeObject(string msg, Type type)
        {
            var o = new WuoObject(type);

            while (msg.Length > 0)
            {
                var name_size = decode_size(ref msg);
                if (msg.Length < name_size || name_size <= 0) break;
                var name = msg.Substring(0, name_size);
                msg = msg.Substring(name_size);
                var data_size = decode_size(ref msg);
                if (msg.Length < data_size || data_size <= 0) break;
                var val = deserial_obj(msg.Substring(0, data_size));
                msg = msg.Substring(data_size);

                o.write(name, val);
            }
            return o.ToObject();
        }

        public string SerializeObject(object obj)
        {
            var output = string.Empty;
            var o = new WuoObject(obj);

            foreach (var name in o.LoopPropNames())
            {
                var val = o.read(name);
                var bag = serial_obj(val);
                if (string.IsNullOrEmpty(bag)) continue;

                output += encode_size(name.Length) + name + encode_size(bag.Length) + bag;
            }

            return output;
        }
        internal static string encode_size(int size)
        {
            var hex = FastEncode.EncodeHex(BitConverter.GetBytes(size));
            var start = hex[0];
            var lt_size = 8 - hex.TrimStart(start).Length;
            if (lt_size == 8) return $"!{start}";

            var end = hex[7];
            var rt_size = 8 - hex.TrimEnd(end).Length;
            lt_size <<= 3;
            lt_size |= rt_size;
            return $"{(char)(64 + lt_size)}{start}{end}{hex.TrimStart(start).TrimEnd(end)}";
        }
        internal static int decode_size(ref string msg)
        {
            if (msg[0] == '!')
            {
                var _n = msg[1];
                msg = msg.Substring(2);
                return BitConverter.ToInt32(FastEncode.DecodeHex(helper.repeat(_n, 8)), 0);
            }

            var size = (byte)msg[0] - 64;
            var lt_size = (size >> 3) & 0b111;
            var rt_size = size & 0b111;
            size = 8 - lt_size - rt_size;
            var start = msg[1];
            var end = msg[2];
            msg = msg.Substring(3);
            var _msg ="";
            _msg += helper.repeat(start, lt_size);
            if (size > 0)
            {
                _msg += msg.Substring(0, size);
                msg = msg.Substring(size);
            }
            _msg += helper.repeat(end, rt_size);
            return BitConverter.ToInt32(FastEncode.DecodeHex(_msg), 0);
        }
        internal static string serial_obj(object? val)
        {
            switch (val)
            {
                case null: return "?";
                case string text: return "t" + text;
                case bool _bool: return _bool ? "+" : "-";
                case int _int: return "i" + encode_size(_int);
                case short _short: return "s" + FastEncode.EncodeHex(BitConverter.GetBytes(_short));
                case uint _uint: return "I" + FastEncode.EncodeHex(BitConverter.GetBytes(_uint));
                case ushort _ushort: return "S" + FastEncode.EncodeHex(BitConverter.GetBytes(_ushort));
                case byte _byte: return "b" + FastEncode.EncodeHex(BitConverter.GetBytes(_byte));
                case byte[] _bytes: return "B" + FastEncode.Base64Encode(_bytes);
                case long _long: return "l" + FastEncode.EncodeHex(BitConverter.GetBytes(_long));
                case ulong _ulong: return "L" + FastEncode.EncodeHex(BitConverter.GetBytes(_ulong));
                case float _float: return "f" + FastEncode.EncodeHex(BitConverter.GetBytes(_float));
                case double _double: return "D" + FastEncode.EncodeHex(BitConverter.GetBytes(_double));
                case char _char: return "c" + FastEncode.EncodeHex(BitConverter.GetBytes(_char));
                case decimal _decimal: return "d" + _decimal.ToString();
                case DateTime _date: return "U" + FastEncode.EncodeHex(BitConverter.GetBytes(_date.Ticks));
                case Guid _guid: return "g" + FastEncode.Base64Encode(_guid.ToByteArray());
            }
            var bag = string.Empty;
            switch (val)
            {
                case IDictionary<string, string> idict_str:
                    bag = "A";
                    foreach (var dict_key in idict_str.Keys)
                    {
                        bag += encode_size(dict_key.Length) + dict_key;
                        if (idict_str[dict_key] == null) bag += '?';
                        else bag += encode_size(idict_str[dict_key].Length) + idict_str[dict_key];
                    }
                    break;
                case List<string> ilist_str:
                    bag = "T";
                    foreach (var list_str in ilist_str) if (list_str == null) bag += '?'; else bag += encode_size(list_str.Length) + list_str;

                    break;
                case Array _array:
                    bag = "*";
                    switch (_array)
                    {
                        case string[] _arr_str:
                            bag += "t";
                            foreach (var _arr_str_item in _arr_str) if (_arr_str_item == null) bag += '?'; else bag += encode_size(_arr_str_item.Length) + _arr_str_item;
                            break;
                        case bool[] _arr_bool:
                            foreach (var _arr_bool_item in _arr_bool) bag += _arr_bool_item ? "+" : "-";
                            break;
                        case int[] _arr_int:
                            bag += "i";
                            foreach (var _arr_int_item in _arr_int) bag += encode_size(_arr_int_item);
                            break;
                        default:
                            bag = string.Empty;
                            break;
                    }
                    break;
            }
            return bag;
        }
        internal static object? deserial_obj(string msg)
        {
            switch (msg[0])
            {
                case '+': return true;
                case '-': return false;
                case '?': return null;
                case 't':
                    return msg.Substring(1);
                case 'i':
                    msg = msg.Substring(1);
                    return decode_size(ref msg);
                case 's':
                    return BitConverter.ToInt16(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'I':
                    return BitConverter.ToUInt32(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'S':
                    return BitConverter.ToUInt16(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'c':
                    return BitConverter.ToChar(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'b':
                    return (byte)BitConverter.ToChar(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'B':
                    return FastEncode.Base64Decode2Bytes(msg.Substring(1));
                case 'l':
                    return BitConverter.ToInt64(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'L':
                    return BitConverter.ToUInt64(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'f':
                    return BitConverter.ToSingle(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'g':
                    return new Guid(FastEncode.Base64Decode2Bytes(msg.Substring(1)));
                case 'D':
                    return BitConverter.ToDouble(FastEncode.DecodeHex(msg.Substring(1)), 0);
                case 'd':
                    return decimal.Parse(msg.Substring(1));
                case 'U':
                    return new DateTime(BitConverter.ToInt64(FastEncode.DecodeHex(msg.Substring(1)), 0));
                case 'A':
                    msg = msg.Substring(1);
                    var dict_str = new Dictionary<string, string>();
                    while (msg.Length > 0)
                    {
                        var size = decode_size(ref msg);
                        if (msg.Length < size || size <= 0) break;
                        var dict_key = msg.Substring(0, size);
                        msg = msg.Substring(size);
                        if (msg[0] == '?')
                        {
                            msg = msg.Substring(1);
                            dict_str[dict_key] = null;
                            continue;
                        }
                        size = decode_size(ref msg);
                        if (size == 0)
                        {
                            dict_str[dict_key] = string.Empty;
                            continue;
                        }

                        if (msg.Length < size || size <= 0) break;
                        var dict_val = msg.Substring(0, size);
                        msg = msg.Substring(size);
                        dict_str[dict_key] = dict_val;
                    }
                    return dict_str;
                case 'T':
                    msg = msg.Substring(1);
                    var list_str = new List<string>();
                    while (msg.Length > 0)
                    {
                        if (msg[0].Equals("?"))
                        {
                            msg = msg.Substring(1);
                            list_str.Add(null);
                            continue;
                        }
                        var size = decode_size(ref msg);
                        if (size == 0)
                        {
                            list_str.Add(string.Empty);
                            continue;
                        }
                        if (msg.Length < size || size <= 0) break;
                        var _list_str = msg.Substring(0, size);
                        msg = msg.Substring(size);
                        list_str.Add(_list_str);
                    }

                    return list_str;
                case '*':
                    msg = msg.Substring(1);

                    var _arr_typ = msg[0];
                    msg = msg.Substring(1);

                    switch (_arr_typ)
                    {
                        case 't':
                            var _arr_t = new Queue<string>();
                            while (msg.Length > 0)
                            {
                                if (msg[0] == '?')
                                {
                                    msg = msg.Substring(1);
                                    _arr_t.Enqueue(null);
                                    continue;
                                }
                                var size = decode_size(ref msg);
                                if (size == 0)
                                {
                                    _arr_t.Enqueue(string.Empty);
                                    continue;
                                }
                                if (msg.Length < size || size <= 0) break;
                                var _arr_str_item = msg.Substring(0, size);
                                msg = msg.Substring(size);
                                _arr_t.Enqueue(_arr_str_item);
                            }
                            return _arr_t.ToArray();
                        case '+':
                        case '-':
                            var _arr_bool = new Queue<bool>();
                            _arr_bool.Enqueue(_arr_typ == '+');
                            while (msg.Length > 0)
                            {
                                if (msg[0] == '+') _arr_bool.Enqueue(true);
                                else _arr_bool.Enqueue(false);
                                msg = msg.Substring(1);
                            }
                            return _arr_bool.ToArray();
                        case 'i':
                            var _arr_int = new Queue<int>();
                            while (msg.Length > 0) _arr_int.Enqueue(decode_size(ref msg));
                            return _arr_int.ToArray();
                    }
                    break;
            }
            return null;
        }
    }
}
